package com.ease.framework {

import com.ease.util.Diagnostics;
import com.ease.util.ObjectEvent;
import com.ease.util.ObjectFactory;
import com.ease.util.ObjectFilter;
import com.ease.util.ReflectionUtil;
import com.ease.util.RelationshipUpdateEvent;
import com.ease.util.Set;
import com.ease.util.TaggedPropertyInfo;
import com.ease.util.WeakReference;
import com.ease.util.collections.TypeBasedObjectCache;
import com.ease.util.events.QueuingEventDispatcher;
import com.ease.util.gc.SharedReference;

import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.utils.Dictionary;
import flash.utils.getQualifiedClassName;

import mx.core.FlexGlobals;
import mx.core.UIComponent;
import mx.events.FlexEvent;

import spark.components.Application;


[Event(name="objectAdded", type="com.ease.util.ObjectEvent")]
[Event(name="objectRemoved", type="com.ease.util.ObjectEvent")]
[Event(name="roleAssigned", type="com.ease.util.RelationshipUpdateEvent")]

public class ViewRegistry extends EventDispatcher
                          implements IObjectModelSource, IObjectDirectory {
   private static const TAG_INJECTABLE_VIEW:String = "InjectableView|View";
   private static var NONE_MEDIATOR:Object = new Object();
   private static var _instance:ViewRegistry;
   /** We keep track of all app-defined views (as opposed to flex library views) even if
    *  they don't have mediators associated with them. */
   private static var _registryByView:Dictionary = new Dictionary();
   private static var _registries:Set/*ViewRegistry*/ = new Set( true );
   private var _injectionMgrRef:SharedReference =
         SharedReference.newReference(InjectionManager);
   private var _contextPropagationMgrRef:SharedReference =
         SharedReference.newReference(ContextPropagationManager);
   private var _rootView:DisplayObject;
   private var _mediatorByView:Dictionary = new Dictionary();
   /** This mapping is per instance of ViewRegistry so that each view domain can have a
    *  different default mediator type for the same view type. */
   private var _mediatorTypeByViewType:Dictionary = new Dictionary();
   private var _objectsByClassCache:TypeBasedObjectCache = new TypeBasedObjectCache();
   /** Keeps track of views that have been notified explicitly using an ObjectEvent. These
    *  views may not yet be added to display, nor yet notified through a
    *  RelationshipUpdateEvent, which means they may not be fully added to this registry
    *  either. */
   private var _explicitlyTrackedViews:Set = new Set( true );
   private var _mediators:Set/*?*/ = new Set( true );
   /** Weak references to mediators for the views that have been removed from this
    *  registry (but are still referenced by the application). These views may come back
    *  to the display, in which case, we'd like to remember their mediator mappings. */
   private var _mediatorWrByRemovedView:Dictionary = new Dictionary( true );
   private var _objectRegistryRef:SharedReference =
         SharedReference.newReference(ObjectRegistry);
   /** The types within these namespaces are excluded from auto-registration. */
   private var _excludedTypePrefixes:Array/*String*/ = [];
   private var _eventBusRef:SharedReference = SharedReference.newReference(EventBus);
   private var _proxyRegistryRef:SharedReference =
         SharedReference.newReference(ProxyRegistry);

   /**
    * The events raised by this class go through this queue-based dispatcher. The queue is
    * flushed when some high-level method completes, where the method may involve a complex
    * interleaving of state changes and event notifications made through various helper
    * methods. This helps ensure that all the interal state changes are applied before
    * notifying the clients of this class about those changes, while maintaining proper
    * ordering of the events. If you forget to flush explicitly, the events are dispatched
    * on the next frame/refresh (i.e., they don't stay pending indefinitely).
    */
   private var _dispatcher:QueuingEventDispatcher = new QueuingEventDispatcher();

   public static const MEDIATOR_ROLE_ID:String = "mediator";

   /**
    * A global instance of this class. This is the instance that would typically be used
    * by an application.
    *
    * <p>This global instance is also used to notify the creation of additional instances
    * of this class using the <code>objectAdded</code> event.
    *
    * @return
    *    An instance of ViewRegistry for global use.
    */
   public static function get instance():ViewRegistry {
      if (_instance == null) {
         _instance = new ViewRegistry( EventBus.instance, ObjectRegistry.instance,
                                       ProxyRegistry.instance, InjectionManager.instance,
                                       ContextPropagationManager.instance );
         _instance.globalInstanceInit();
      }
      return _instance;
   }

   [ArrayElementType("com.ease.framework.ViewRegistry")]
   /**
    * All active instances of this class.
    */
   public static function get instances():Array/*ViewRegistry*/ {
      var regs:Array/*ViewRegistry*/ = _registries.toArray();
      return regs;
   }

   /** @private */
   public static function getRegistry( view:DisplayObject ):ViewRegistry {
      return _registryByView[view];
   }

   /**
    * Returns the view associated with the given object assuming it's a mediator. The
    * lookup may rely on the [InjectableView] (or [View]) property of the given object,
    * so it's a must that the mediator define such a property in order to ascertain that
    * this lookup would succeed.
    */
   public static function getViewFromMediator( object:Object ):DisplayObject {
      if (object == null) {
         return null;
      }
      var view:Object =
            ReflectionUtil.getTaggedPropertyValue( object, TAG_INJECTABLE_VIEW );
      return view as DisplayObject;
   }

   /**
    * Initializes an instance of this class. It can be used to create a separate view
    * registry independent of the global registry available through the instance property.
    * This, for example, allows you to create a system of registries or services
    * (consisting of EventBus, ViewRegistry, CommandRegistry, ProxyRegistry,
    * ContextPropagationManager, etc.) that is unconnected from the global instances of
    * these classes.
    *
    * @param eventBus
    *    The EventBus associated with this registry. The events raised by the mediators in
    *    this registry would be sent on this bus. Similarly, any events posted on this bus
    *    would be routed to the mediators that have registered for those events.
    *
    * @param proxyRegistry
    *    The ProxyRegistry associated with this registry, if any. The proxy registry would
    *    be used to inject proxies into the mediators in this registry if they have any
    *    [InjectableProxy] properties.
    *
    * @param injectionMgr
    *    The InjectionManager associated with this registry.
    *
    * @param propertyPropagationMgr
    *    The ContextPropagationManager associated with this registry.
    *
    * @see #instance
    */
   public function
   ViewRegistry( eventBus:EventBus = null, objectRegistry:ObjectRegistry = null,
                 proxyRegistry:ProxyRegistry = null, injectionMgr:InjectionManager = null,
                 contextPropagationMgr:ContextPropagationManager = null ) {
      _dispatcher.target = this;
      _dispatcher.filterFunction = isPendingEventRelevant;
      _registries.add( this );
      if (eventBus == null) {
         eventBus = new EventBus();
      }
      setEventBus(eventBus);
      this.injectionMgr = injectionMgr;
      if (injectionMgr != null) {
         injectionMgr.addProvider( this );
      }
      if (proxyRegistry == null) {
         proxyRegistry = new ProxyRegistry( this.eventBus, this.injectionMgr )
      }
      setProxyRegistry(proxyRegistry);
      this.contextPropagationMgr = contextPropagationMgr;
      // Listen for mediator assignments and object add/remove notifications.
      eventBus.addEventListener( RelationshipUpdateEvent.ROLE_ASSIGNED,
                                  onRoleAssignment );
      eventBus.addEventListener( ObjectEvent.OBJECT_ADDED, onObjectAdded );
      eventBus.addEventListener( ObjectEvent.OBJECT_REMOVED, onObjectRemoved );
      // Set up the associated object registry. We rely on the object registry to take
      // care of context propagation and property injection on behalf of this registry.
      if (objectRegistry == null) {
         objectRegistry = new ObjectRegistry( this.eventBus, this.injectionMgr,
               this.contextPropagationMgr);
      }
      this.objectRegistry = objectRegistry;
      this.objectRegistry.addChildRegistry( this );
      // Announce the creation of a new view registry.
      if (_instance != null) {
         _instance.postEvent( ObjectEvent.newAddition( this ) );
      }
   }

   //------------------------------------------------------------------------------------
   // Instance properties
   // The property provider used avoids directly referencing the global instance.
   // This is done to simplify the object graph for Memory Profiler.

   //-------------------------------------------------------
   // Property: eventBus

   /**
    * The EventBus associated with this registry. The events raised by the mediators in
    * this registry would be sent on this bus. Similarly, any events posted on this bus
    * would be routed to the mediators that have registered for those events.
    *
    * @return
    *    The associated event bus.
    */
   public function get eventBus():EventBus {
      return _eventBusRef.object;
   }

   private function setEventBus(value:EventBus):void {
      _eventBusRef.object = value;
   }

   //-------------------------------------------------------
   // Property: proxyRegistry

   /**
    * The ProxyRegistry associated with this registry. The proxy registry would be used
    * to inject proxies into the mediators in this registry if they have any
    * [InjectableProxy] properties.
    *
    * @return
    *    The associated proxy registry.
    */
   [Optional]
   public function get proxyRegistry():ProxyRegistry {
      return _proxyRegistryRef.object;
   }

   private function setProxyRegistry(value:ProxyRegistry):void {
      _proxyRegistryRef.object = value;
   }

   //-------------------------------------------------------
   // Property: objectRegistry

   private function get objectRegistry():ObjectRegistry {
      return _objectRegistryRef.object;
   }

   private function set objectRegistry(value:ObjectRegistry):void {
      _objectRegistryRef.object = value;
   }

   //-------------------------------------------------------
   // Property: injectionManager

   private function get injectionMgr():InjectionManager {
      return _injectionMgrRef.object;
   }

   private function set injectionMgr(value:InjectionManager):void {
      _injectionMgrRef.object = value;
   }

   //-------------------------------------------------------
   // Property: contextPropagationManager

   private function get contextPropagationMgr():ContextPropagationManager {
      return _contextPropagationMgrRef.object;
   }

   private function set contextPropagationMgr(value:ContextPropagationManager):void {
      _contextPropagationMgrRef.object = value;
   }

   //-------------------------------------------------------
   // Property: treatsViewsAsMediators

   private var _treatsViewsAsMediators:Boolean = false;

   /**
    * Specifies whether views are treated like mediators (for the purposes of application
    * event dispatch, proxy injection, etc.).
    *
    * <p>If an application splits its display into views and mediators, it's the
    * mediators' responsibility to communicate with the rest of the world on the views'
    * behalf. They might do this by raising application events, i.e., by listening to the
    * primitive ui events and translating them into events that are meaningful to the
    * rest of the application. The events raised by a view are for its mediator and other
    * views that know about the said view, but they don't signify events of general
    * interest to the application.
    *
    * <p>However, frinje optionally allows the views to behave like mediators as well.
    * This option is enabled by setting the <code>treatsViewsAsMediators</code>
    * property to true.
    */
   public function get treatsViewsAsMediators():Boolean {
      return _treatsViewsAsMediators;
   }

   /** @private */
   public final function set treatsViewsAsMediators( value:Boolean ):void {
      Diagnostics.assert( _rootView == null,
            "This property must be set before the registry is initialized; " +
            "else, behavior is undefined." );
      _treatsViewsAsMediators = value;
   }

   //---------------------------------------------------------------------------
   // Instance Methods

   /** @inheritDoc */
   public final function contains( item:Object ):Boolean {
      if (_mediators.contains( item )) {
         return true;
      }
      if (_registryByView[item] == this) {
         return true;
      }
      return false;
   }

   /**
    * Adds a type name prefix for the classes whose instances should be excluded from
    * auto-registration. The default values are "mx." and "flash.".
    */
   public final function addExcludedTypePrefix( prefix:String ):void {
      if (_excludedTypePrefixes.indexOf( prefix ) >= 0) {
         return;
      }
      _excludedTypePrefixes.push( prefix );
   }

   /**
    * The method looks up the existing views and mediators only (i.e., does not create
    * any new ones). Also, only the concrete type of the object is considered when
    * looking for a match with the <code>type</code> in the spec (i.e., no base classes
    * or interfaces of the objects are considered).
    *
    * @see IObjectDirectory#getObjects()
    */
   public final function getObjects( spec:ObjectFilter, callback:Function,
                                     moduleReference:* = null,
                                     currentObjectSet:Array/*?*/ = null ):void {
      if (spec.metaPropertyFilterCount > 0) {
         // We don't filter on any meta-properties, which means that if there is some
         // meta-property filter defined, we can't ensure that our objects satisfy this
         // filter. Since the object provider (i.e., this directory) is supposed to
         // ascertain the meta-property filter, we have to return empty from here.
         callback( [] );
         return;
      }
      var objects:Array = _objectsByClassCache.getObjects( spec.type );
      objects = spec.filter( objects );

      // Note: It'd be nice to find the intersection between currentObjectSet and objects,
      // and place the common items at the front of the array returned from here (in the
      // same order as they are in currentObjectSet), but doing so hasn't been found
      // necessary so far.

      callback( objects );
   }

   /** @inheritDoc */
   public final function evaluateMetaFilter( spec:ObjectFilter, object:* ):Boolean {
      // This class doesn't maintain any meta-properties on its objects (i.e., views and
      // mediators).
      return true;
   }

   /**
    * Adds a MediatorMap instance to this registry. A MediatorMap contains mappings from
    * view types to their default mediator types.
    *
    * @param map
    *    The mediator map to be added.
    */
   public final function addMediatorMap( map:MediatorMap ):void {
      if (map == null) {
         return;
      }
      for each (var mapping:MediatorMapping in map.mappings) {
         var mediatorClass:Class = mapping.mediator;
         if (! mediatorClass) {
            mediatorClass = NoneClass;
         }
         _mediatorTypeByViewType[mapping.view] = mediatorClass;
      }
   }

   /**
    * Adds the given view to the registry, and also adds the given mediator, if any,
    * against the view being registered.
    *
    * <p>A view can have only one mediator registered against it. Calling this method
    * again with a different mediator would overwrite the earlier registration.
    *
    * <p>The method automatically sets the [InjectableView] property of the mediator. If
    * the view is already registered against another mediator, the method also resets the
    * [InjectableView] property of the existing mediator to null.
    *
    * <p>If the view is not already initialized (in the flex sense), the registration is
    * deferred and remains pending until view's creation is complete.
    *
    * @param view
    *    The view to be added to the registry.
    *
    * @param mediator
    *    An optional mediator to register against the given view.
    *
    * @param isRootView
    *    Indicates whether the given view should serve as the root view for this registry.
    *    A registry uses its root view to discover the child views as they're added to the
    *    root view at any nested level. The value is ignored for the first view added to a
    *    view registry for it is automatically treated as a root view.
    *
    *    <p>The global Application instance and the SystemManager instance are considered
    *    equivalent for the purposes of defining a root view. This means that either of
    *    them can be passed-in when setting up the top-level registry. Also, there should
    *    exist only one registry that has either of them as its root view.
    */
   public final function addView( view:DisplayObject, mediator:Object = null,
                                  isRootView:Boolean = false ):void {
      addView_Internal( view, mediator, isRootView );
      // Pending events are not explicitly dispatched so that the added view is processed
      // by the other platform services in the next frame. This is an optimization to
      // shorten the delay before the added view is first rendered.
   }

   /**
    * Removes the given view and the associated mediator from the registry. Removing the
    * objects from the event bus associated with the registry is also part of this
    * removal.
    *
    * @param view
    *    The view to be removed from the registry.
    *
    * @return
    *    Returns true if the view is found in the registry and removed; false otherwise.
    */
   public final function removeView( view:DisplayObject ):Boolean {
      var foundAndRemoved:Boolean = removeView_Internal( view );
      // Need to dispatch immediately because views shouldn't continue to receive any
      // service if the client has decided to remove it from stage.
      // E.g., context properties shouldn't be propagated to it (because they may not be
      // suitable for those views).
      _dispatcher.dispatchPendingEvents();
      return foundAndRemoved;
   }

   public static function getMediator( view:DisplayObject,
         includeInactive:Boolean = false ):Object {
      var mediator:Object = getMediator_Internal( view );
      if (mediator == null || mediator == NONE_MEDIATOR) {
         if (!includeInactive) {
            return null;
         }
         // look up the view in all registries' previously known views
         for each (var registry:ViewRegistry in instances) {
            mediator = registry.getMediatorIfViewPreviouslyKnown(view);
            if (mediator != null) {
               break;
            }
         }
      }
      return mediator;
   }

   /** @see #removeView() */
   private function removeView_Internal( view:DisplayObject ):Boolean {
      if (view == null || _registryByView[view] != this) {
         return false;
      }
      var mediator:Object = _mediatorByView[view];
      delete _mediatorByView[view];
      delete _registryByView[view];
      _objectsByClassCache.remove( view );
      if (_treatsViewsAsMediators) {
         this.eventBus.removeParticipant( view );
      }
      processMediator( mediator, OpCode.Remove );
      if (mediator != null) {
         // the given view existed in this registry, so we notify the removal
         postEvent( ObjectEvent.newRemoval( view ) );
      }
      if (view == _rootView) {
         this.rootView = null;
      }
      if (isValidMediator( mediator )) {
         _mediatorWrByRemovedView[view] = new WeakReference( mediator );
      }
      return true;
   }

   private static function getMediator_Internal( view:DisplayObject ):Object {
      if (view == null) {
         return null;
      }
      // Get the mediator from the registry that the view belongs to.
      var registry:ViewRegistry = _registryByView[view] as ViewRegistry;
      if (registry == null) {
         return null;
      }
      return registry.mediatorFor( view );
   }

   private function mediatorFor( view:DisplayObject ):Object {
      var mediator:Object = _mediatorByView[view];
      return mediator;
   }

   private function globalInstanceInit():void {
      var app:EventDispatcher = FlexGlobals.topLevelApplication as EventDispatcher;
      // The global instance of this class listens for the mediator assignment and object
      // add/remove events on the global Application object as well, i.e., not just on the
      // associated event bus (so that even those objects can raise them which are not
      // already on some event bus).
      // Note that we don't (need to) use weak-referencing here (i.e., when adding the
      // listeners) because this global instance is for the lifetime of the app.
      // Also note that multiple notifications (i.e., from the bus as well as from the
      // Application, if that ever happens) are fine since object add/remove in this
      // class is idempotent.
      app.addEventListener( RelationshipUpdateEvent.ROLE_ASSIGNED, onRoleAssignment );
      app.addEventListener( ObjectEvent.OBJECT_ADDED, onObjectAdded );
      app.addEventListener( ObjectEvent.OBJECT_REMOVED, onObjectRemoved );
   }

   /** @see #addView() */
   private function addView_Internal( view:DisplayObject, mediator:Object = null,
                                      isRootView:Boolean = false ):void {
      if (mediator == null) {
         mediator = NONE_MEDIATOR;
      }
      var currentMediator:Object = getMediator_Internal( view );
      if (currentMediator == mediator) {
         return;
      }
      var isViewNewToThisRegistry:Boolean = registerView( view, mediator, isRootView );

      if (view is UIComponent && ! UIComponent( view ).initialized) {
         // If the view is not initialized yet, defer till its creation is complete.
         // CREATION_COMPLETE event is used rather than the INITIALIZE event in case
         // this method is called from within an INITIALIZE handler, in which case,
         // initialized property would be false, but this callback would never be invoked.
         var viewInitCallback:Function = function( ev:Event = null ):void {
            view.removeEventListener( FlexEvent.CREATION_COMPLETE, viewInitCallback );
            setupViewAndMediator( view, mediator, isViewNewToThisRegistry );
         }
         view.addEventListener( FlexEvent.CREATION_COMPLETE, viewInitCallback );
         return;
      }
      setupViewAndMediator( view, mediator, isViewNewToThisRegistry );
   }

   /**
    * Registers the given view and the mediator, in the sense of marking them as
    * registered with this registry, but does not necessarily make them available for
    * object directory lookups. The return value indicates whether the view is new to
    * this registry or not. If not new, it may be that it's being registered against a
    * new mediator.
    */
   private function registerView( view:DisplayObject, mediator:Object,
                                  isRootView:Boolean ):Boolean {
      var currentMediator:Object = getMediator_Internal( view );
      var currentRegistry:ViewRegistry = _registryByView[view];
      if (currentRegistry != null && currentRegistry != this) {
         currentRegistry.removeView( view );
      }
      // delete the view from our memory of previously removed views
      delete _mediatorWrByRemovedView[view];
      // dissociate the view from its current mediator, if any
      setViewOnMediator( null, currentMediator );
      // Mark the view as registered at the earliest, so that any lookup for the view's
      // mediator would work as expected.
      _registryByView[view] = this;
      _mediatorByView[view] = mediator;
      // Also register the objects with the event bus at the earliest (i.e., before
      // leaving this function) so that if they raise any events during their
      // initialization, those events would be raised on the bus.
      if (_treatsViewsAsMediators) {
         this.eventBus.addParticipant( view );
      }
      if (isValidMediator( mediator )) {
         this.eventBus.addParticipant( mediator );
      }

      if (isRootView || _rootView == null) {
         this.rootView = view;
      }
      var isViewNewToThisRegistry:Boolean = currentRegistry != this;
      return isViewNewToThisRegistry;
   }

   private function setupViewAndMediator( view:DisplayObject, mediator:Object,
                                          isViewNewToThisRegistry:Boolean = true ):void {
      if (isViewNewToThisRegistry) {
         // the view is not already registered with this registry
         _objectsByClassCache.add( view );
         if (_treatsViewsAsMediators) {
            setInjectableProxiesOn( view );
         }
         // We make sure that the view's properties are injected before it's handed over
         // to the mediator, so that the latter's view setter can start working with the
         // view right away.
         this.injectionMgr.addTarget( view );
         postEvent( ObjectEvent.newAddition( view ) );
      }
      processMediator( mediator, OpCode.Add, new InjectableViewSetting( view ) );
   }

   private function postEvent( event:Event ):void {
      _dispatcher.enqueue( event );
   }

   /**
    * Processes notifications of mediator additions from the client (as opposed to the
    * mediators created by ViewRegistry itself).
    */
   private function onRoleAssignment( ev:RelationshipUpdateEvent ):void {
      if (ev.relationship != MEDIATOR_ROLE_ID || !(ev.object is DisplayObject)) {
         return;
      }
      var view:DisplayObject = DisplayObject( ev.object );
      var newMediator:Object = ev.relatedObject;
      // addView itself takes care of removing the view's current mediator, if any
      addView( view, newMediator );
   }

   private static function isValidMediator( object:Object ):Boolean {
      return object != null && object != NONE_MEDIATOR;
   }

   /**
    * Adds or removes the given mediator to/from the internal caches, etc. Removal also
    * removes it from the event bus and optionally re-initializes its [InjectableView]
    * property.
    *
    * <p>All mediator additions/removals must go through this helper method.
    */
   private function processMediator( mediator:Object, op:OpCode,
                                     viewSetting:InjectableViewSetting = null ):void {
      if (! isValidMediator( mediator )) {
         return;
      }
      if (op == OpCode.Remove) {
         if (viewSetting != null) {
            setViewOnMediator( viewSetting.value, mediator );
         }
         this.eventBus.removeParticipant( mediator );
         _objectsByClassCache.remove( mediator );
         _mediators.remove( mediator );
         postEvent( ObjectEvent.newRemoval( mediator ) );
         return;
      }
      if (op == OpCode.Add) {
         _mediators.add( mediator );
         _objectsByClassCache.add( mediator );
         // Add the mediator to the event bus before setting the view property on it,
         // so that the mediator is ready to roll when its setters are invoked.
         this.eventBus.addParticipant( mediator );
         // We do our own invocations (view injection in this case) on the mediator before
         // we give a chance to others to do it (as a side-effect of notifying the Add
         // ObjectEvent).
         if (viewSetting != null) {
            setViewOnMediator( viewSetting.value, mediator );
         }
         postEvent( ObjectEvent.newAddition( mediator ) );
         return;
      }
   }

   private function setViewOnMediator( view:DisplayObject, mediator:Object ):void {
      if (! isValidMediator( mediator )) {
         return;
      }
      var pi:TaggedPropertyInfo =
            ReflectionUtil.getSingleTaggedProperty( mediator, TAG_INJECTABLE_VIEW );
      var currentlyAssociatedView:Object;
      if (pi != null && pi.access != ReflectionUtil.ACCESS_WRITEONLY) {
         currentlyAssociatedView = mediator[pi.name];
         if (currentlyAssociatedView == view) {
            return;
         }
      }
      var ev:RelationshipUpdateEvent;
      if (view != null) {
         ev = RelationshipUpdateEvent.newRoleAssignment( view, mediator,
                                                         MEDIATOR_ROLE_ID );
      } else if (currentlyAssociatedView != null) {
         ev = RelationshipUpdateEvent.newRoleUnassignment( currentlyAssociatedView,
                                                           MEDIATOR_ROLE_ID );
         ev.oldRelatedObject = mediator;
      }
      if (ev != null) {
         postEvent( ev );
      }
      if (pi == null) {
         return;
      }
      mediator[pi.name] = view;
   }

   /**
    * For a root view, we need to start listening to its 'added' and 'removed' events,
    * so that we can find out when a new view is being added to the stage, and can
    * automatically initialize a mediator for it if appropriate.
    *
    * <p>If the root is Application, we instead listen to the SystemManager for the
    * bubbled up events, in order to account for the popup views just as well as the
    * regular views. This is so because views added through the PopUpManager don't fall
    * in the Application view hierarchy.
    */
   private function set rootView( root:DisplayObject ):void {
      if (root == _rootView) {
         return;
      }
      var rootEventSrc:IEventDispatcher;
      if (_rootView != null) {
         rootEventSrc = _rootView;
         if (isApplicationObject( _rootView )) {
            rootEventSrc = UIComponent( _rootView ).systemManager;
         }
         rootEventSrc.removeEventListener( Event.ADDED, onViewAddedToDisplay );
         rootEventSrc.removeEventListener( Event.REMOVED, onViewRemovedFromDisplay );
         processDescendantViews( _rootView as DisplayObjectContainer, OpCode.Remove );
      }
      _rootView = root;
      if (_rootView != null) {
         rootEventSrc = _rootView;
         if (isApplicationObject( _rootView )) {
            rootEventSrc = UIComponent( _rootView ).systemManager;
         }
         rootEventSrc.addEventListener( Event.ADDED, onViewAddedToDisplay );
         rootEventSrc.addEventListener( Event.REMOVED, onViewRemovedFromDisplay );
         // harvest already created child views and add them to our record
         processDescendantViews( _rootView as DisplayObjectContainer, OpCode.Add );
      }
   }

   private function isApplicationObject( view:DisplayObject ):Boolean {
      return view is mx.core.Application || view is spark.components.Application;
   }

   private function
   processDescendantViews( view:DisplayObjectContainer, op:OpCode ):void {
      if (view == null) {
         return;
      }
      var currentRegistry:ViewRegistry = _registryByView[view];
      if (currentRegistry != null && currentRegistry != this) {
         // The view belongs to some other registry; going further is wasteful at best.
         return;
      }
      for (var index:uint = 0; index < view.numChildren; index++) {
         var childView:DisplayObject = view.getChildAt( index );
         switch (op) {
         case OpCode.Add:
            addDiscoveredView( childView );
            break;
         case OpCode.Remove:
            removeView( childView );
            break;
         }
         if (childView is DisplayObjectContainer) {
            processDescendantViews( DisplayObjectContainer( childView ), op );
         }
      }
   }

   private function
   processDescendantViewsInclusive( view:DisplayObject, op:OpCode ):void {
      if (view == null) {
         return;
      }
      var viewSuccessfullyProcessed:Boolean = false;
      if (op == OpCode.Add) {
         viewSuccessfullyProcessed = addDiscoveredView( view );
      } else if (op == OpCode.Remove) {
         viewSuccessfullyProcessed = removeView( view );
      }
      if (! (view is DisplayObjectContainer)) {
         return;
      }
      if (viewSuccessfullyProcessed || isOfExcludedType( view )) {
         // Either it's an app-defined view that has just been added/removed to/from this
         // registry, or it's a standard flex container object that may contain such
         // app-defined views as children.
         processDescendantViews( DisplayObjectContainer( view ), op );
      }
   }

   private function onViewAddedToDisplay( ev:Event ):void {
      if (!(ev.target is DisplayObject)) {
         return;
      }
      var view:DisplayObject = ev.target as DisplayObject;
      if (_registryByView[view] != null) {
         // The view is already registered with some view registry, no need to process it
         // further (i.e., possibly watch its creation only to discover later that it
         // shouldn't be added to this registry anyway).
         return;
      }
      processDescendantViewsInclusive( view, OpCode.Add );
   }

   /**
    * @return
    *    Returns true if the given view is actually a new view and the call adds it to the
    *    registry; false otherwise.
    */
   private function addDiscoveredView( view:DisplayObject ):Boolean {
      // Trivially return if it's an excluded type, to save on the reflection
      // cost, since the [DefaultMediator] tag can only show up on an application
      // defined view class.
      if (isOfExcludedType( view )) {
         return false;
      }
      var mediator:Object = getMediator_Internal( view );
      if (isValidMediator( mediator )) {
         // the view is already registered against a mediator; ignore it
         return false;
      }
      mediator = getMediatorIfViewPreviouslyKnown( view );
      if (mediator == null) {
         mediator = newMediator( view );
      }
      addView( view, mediator );
      return true;
   }

   private function getMediatorIfViewPreviouslyKnown( view:DisplayObject ):Object {
      var wr:WeakReference = _mediatorWrByRemovedView[view];
      if (wr == null) {
         return null;
      }
      return wr.object;
   }

   /**
    * Creates a mediator for the given view. Does not set the mediator's [View] property.
    */
   private function newMediator( view:DisplayObject ):Object {
      var mediatorFactory:ObjectFactory = mediatorFactoryFor( view );
      if (mediatorFactory == null) {
         return null;
      }
      var mediator:Object;
      try {
         // we first try to use a construction that takes the view as its only arg; if
         // that fails, we try the no-arg construction below
         mediator = mediatorFactory.newInstance( view );
      } catch (err:Error) {
         // ignore
      }
      if (mediator == null) {
         mediator = mediatorFactory.newInstance();
      }
      if (mediator == null) {
         Diagnostics.assert( false, "Could not create a mediator for view: " +
               getQualifiedClassName(view) +
               ". The mediator name may be missing, incorrect or not fully qualified.");
         return null;
      }
      // TODO: this should be done after the view has been injected into the mediator
      setInjectableProxiesOn( mediator );
      return mediator;
   }

   private function mediatorFactoryFor( view:DisplayObject ):ObjectFactory {
      var viewClass:Class = Object(view).constructor;
      var mediatorClass:Class = _mediatorTypeByViewType[viewClass];
      if (mediatorClass == NoneClass) {
         return null;
      }
      if (mediatorClass != null) {
         return ObjectFactory.newFactoryByClass( mediatorClass );
      }
      // A mediator type wasn't explicitly registered for this view type, or we haven't
      // reflected on the class yet, so lets try to get it from the view class metadata.
      var mediatorClassName:String =
         ReflectionUtil.getClassTagValue( view, "DefaultMediator" );
      var factory:ObjectFactory = ReflectionUtil.newFactory( mediatorClassName, view );
      if (! factory) {
         _mediatorTypeByViewType[viewClass] = NoneClass;
         return null;
      }
      factory.constructionListener = function( mediator:* ):void {
         var mediatorClass:Class = Object(mediator).constructor;
         _mediatorTypeByViewType[viewClass] = mediatorClass;
      }
      return factory;
   }

   private function onViewRemovedFromDisplay( ev:Event ):void {
      var view:DisplayObject = ev.target as DisplayObject;
      if (view == null) {
         return;
      }
      if (_explicitlyTrackedViews.contains( view )) {
         return;
      }
      processDescendantViewsInclusive( view, OpCode.Remove );
   }

   /**
    * We also support mediators (and/or views) exercising proxies directly (in addition
    * to the preferred pattern of going through commands instead).
    */
   private function setInjectableProxiesOn( object:Object ):void {
      this.proxyRegistry.setInjectableProxiesOn( object );
   }

   /**
    * Handles explicit addition of an object to the application (as notified through the
    * global Application instance or the event bus associated with this registry). It may
    * be a non-view object.
    */
   private function onObjectAdded( ev:Event ):void {
      var object:* = ReflectionUtil.getPropertyValue( ev, "object" );
      if (object == null) {
         return;
      }
      // Note: Should not trivially return from here even if this registry already
      // contains the given object. It may be a view that is known to the registry but is
      // not "explicitly tracked" so far, i.e., can be removed from the registry upon
      // removal from the stage; an object event may indicate that the client wants to
      // override that behavior.

      var objectIsView:Boolean = object is DisplayObject;
      if (objectIsView) {
         _explicitlyTrackedViews.add( object );
      }
      if (! objectIsView) {
         // If it's a view, proxy-injection will happen (or must have happened) when the
         // view is (or was) added to the display. So we do it here only if it's not a view.
         setInjectableProxiesOn( object );
      }
   }

   /**
    * Handles explicit removal of an object from the application (as notified through the
    * global Application instance or the event bus associated with this registry). It may
    * be a non-view object.
    */
   private function onObjectRemoved( ev:Event ):void {
      var object:* = ReflectionUtil.getPropertyValue( ev, "object" );
      if (object == null) {
         return;
      }
      if (_explicitlyTrackedViews.contains( object )) {
         _explicitlyTrackedViews.remove( object );
         processDescendantViewsInclusive( object as DisplayObject, OpCode.Remove );
      }
   }

   private function isOfExcludedType( object:Object ):Boolean {
      if (ReflectionUtil.isFlexSkin(object)) {
         // Skins do not publish events like add or remove, so fringe cannot manage them.
         return true;
      }
      var className:String = getQualifiedClassName( object );
      for each (var forbiddenPrefix:String in _excludedTypePrefixes) {
         if (className.indexOf( forbiddenPrefix ) == 0) {
            return true;
         }
      }
      var isExcluded:Boolean = ReflectionUtil.isFlexTypeName(className);
      return isExcluded;
   }

   /**
    * Checks whether the given pending event is still relevant according to the
    * registry state.
    *
    * For example, an object addition event is no longer relevant if the object has been
    * removed from the registry before the postponed addition event is dispatched.
    * Addition events are queued while removal events are immediately dispatched which
    * can lead to dispatching incorrect events.
    */
   private function isPendingEventRelevant( event:Event ):Boolean {
      // only ObjectEvent is checked
      var objectEvent:ObjectEvent = event as ObjectEvent;
      if ( objectEvent == null ) {
         return true;
      }

      // ObjectEvent for this registry is always fine
      if ( objectEvent.object == this ) {
         return true;
      }

      // check if the ObjectEvent is consistent with the current registry state
      if ( objectEvent.type == ObjectEvent.OBJECT_ADDED ) {
         return this.contains( objectEvent.object );
      } else if ( objectEvent.type == ObjectEvent.OBJECT_REMOVED ) {
         return !this.contains( objectEvent.object );
      }

      return true;
   }
} // class

}

import flash.display.DisplayObject;

/**
 * A private class that represents an undefined or none value.
 */
class NoneClass {}

class OpCode
{
   public static var Add:OpCode = new OpCode();
   public static var Remove:OpCode = new OpCode();
}

/**
 * A private class that represents a setting for the [InjectableView] property of a
 * mediator.
 */
class InjectableViewSetting
{
   public var value:DisplayObject;

   public function InjectableViewSetting( value:DisplayObject = null ) {
      this.value = value;
   }
}